//
//  XILayer.m
//  XI Engine
//
//  Created by Balazs Faludi on 21.06.09.
//  Copyright 2009 Balazs Faludi. All rights reserved.
//

#import "XILayer.h"
#import "XIEngine.h"
#import "chipmunk.h"

@implementation XILayer

@synthesize scene;
@synthesize ignoresPause;
@synthesize objects;
@synthesize physicsEnabled;
#if TARGET_OS_IPHONE
@synthesize space;
#endif

#pragma mark -
#pragma mark Constuctor & desctructor

- (id)init {
    if ((self = [super init])) {
		scene = nil;
		ignoresPause = NO;
		NSArray *myObjects = [[NSArray alloc] init];
		self.objects = myObjects;
		[myObjects release];
		physicsEnabled = NO;
    }
    return self;
}

- (void)loadFromDictionary:(NSDictionary *)dict
{
	@try {
		// If the file contains properties of the scene, fo through them and set the scene properties.
		// If the property does not exist, try sending it as a message.
		NSDictionary *properties = [dict objectForKey:@"properties"];
		if(properties)
		{
			NSEnumerator *enumerator = [properties keyEnumerator];
			NSString *propertyName;
			while ((propertyName = [enumerator nextObject])) {
				if(!setValueOfObjectForPropertyName(&self, propertyName, [properties valueForKey:propertyName]))
				{performSelectorOfObject(&self, propertyName, [properties valueForKey:propertyName]);}
			}
		}
		
		// If the file contains objects, init them and add to the scene.
		NSArray *myObjects = [dict objectForKey:@"objects"];
		if(myObjects)
		{
			for(NSDictionary *objectDict in myObjects)
			{
				// Init class using it's strign name representation. Then add to the scene's objects.
				NSString *class = [objectDict valueForKey:@"class"];
				if(!class || [class isEqualToString:@""]){class = @"XIObject";}
				id object = [newObjectWithClassName(class) init];
				[self addObject:object];
				[object loadFromDictionary:objectDict];
				//int a = [object retainCount];
				[object release];
			}
		}
	}
	@catch (NSException * e) {
		[NSException raise:@"XILoadingException" format:@"Layer cannot be loaded from specified dictionary."];
		return;
	}
}

- (id)initFromDictionary:(NSDictionary *)dict
{
	if ((self = [self init])) {
		[self loadFromDictionary:dict];
	}
	return self;
}

- (id)initWithXilName:(NSString *)xilName bundle:(NSBundle *)xilBundle {
	if ((self = [self init])) {
		
		@try {
			// Load the xil file.
			NSString *path = [xilBundle pathForResource:xilName ofType:@"xil"];
			NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
			return [self initFromDictionary:dict];
			
		}
		@catch (NSException * e) {
			[NSException raise:@"XILoadingException" format:@"Xil file \"%@.xil\" not found in bundle's ressource path at \"%@\".", 
				xilName, [xilBundle resourcePath]];
			return nil;
		}
	}
    return self;
}

- (NSMutableDictionary *)saveStateToDictionary
{
	NSMutableArray *objectsArray = [NSMutableArray arrayWithCapacity:30];
	for(XIObject *o in objects)
	{
		NSDictionary *objDict = [o saveToDictionary];
		[objectsArray addObject:objDict];
	}
	return [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:ignoresPause], @"ignoresPause",
			[NSNumber numberWithBool:physicsEnabled], @"physicsEnabled",
			objectsArray, @"objects", nil];
}

- (NSDictionary *)saveToDictionary
{
	NSMutableDictionary *mutable = [NSMutableDictionary dictionaryWithCapacity:2];
	[mutable setObject:[self className] forKey:@"class"];
	[mutable setObject:[self saveStateToDictionary] forKey:@"properties"];
	return mutable;	
}

- (void)dealloc {
	[scene release];
	[objects release];
#if TARGET_OS_IPHONE
	cpSpaceFree(space);
#endif
    [super dealloc];
}

#pragma mark -
#pragma mark Object manipulation

// Add an object to the top of the object array. Objects that are added later will appear on top of others.
- (void)addObject:(XIObject *)object
{
	[object willBeAddedToLayer:self];
	if(object && object.layer){[object.layer removeObject:object];}
	NSMutableArray *mutable = [[NSMutableArray alloc] initWithArray:objects];
	[mutable addObject:object];
	self.objects = mutable;
	[mutable release];
	object.layer = self;
	[object wasAddedToLayer:self];
}

- (void)insertObject:(XIObject *)object atIndex:(int)index
{
	NSMutableArray *mutable = [NSMutableArray arrayWithArray:objects];
	[mutable insertObject:object atIndex:index];
	self.objects = mutable;
	object.layer = self;
}

- (void)removeObject:(XIObject *)object
{
	object.layer = nil;
	NSMutableArray *mutable = [NSMutableArray arrayWithArray:objects];
	for(int i = 0; i < [mutable count]; i++)
	{
		if([mutable objectAtIndex:i] == object)
		{
			[mutable removeObjectAtIndex:i];
		}
	}
	self.objects = mutable;
}

- (NSArray *)objectsAtPoint:(CGPoint)point
{
	NSMutableArray *mutable = [NSMutableArray arrayWithCapacity:5];
	for(XIObject *o in objects)
	{
		if([o isKindOfClass:[XIShape class]])
		{
			XIShape *s = (XIShape *)o;
			CGRect rect = CGRectMake(s.center.x - s.size.width * 0.5, s.center.y - s.size.height * 0.5, s.size.width, s.size.height);
			if(CGRectContainsPoint(rect, point))
			{
				[mutable addObject:s];
			}
		}
	}
	return mutable;
}

- (XIObject *)objectAtPoint:(CGPoint)point
{
	
}

#pragma mark -
#pragma mark Physics

// We only need physics if the app is running on the iPhone.
#if TARGET_OS_IPHONE

// Enables physics simulation for the scene.
- (void)enablePhysics
{
	if(physicsEnabled){return;}
	if(!space){[self setupChipmunk];}
	timeAccumulator = 0.0;
	physicsEnabled = YES;
}

// Disabled physics simulation. The space is not destroyed so you can simple enable physics again to continue.
- (void)disablePhysics
{
	if(!physicsEnabled){return;}
	physicsEnabled = NO;
}

- (void)setPhysicsEnabled:(BOOL)enabled
{
	enabled ? [self enablePhysics] : [self disablePhysics];
}

// Updates a shape's visual representation.
void updateShape(void *ptr, void* unused)
{  
	// Get our shape.
	cpShape *shape = (cpShape*)ptr;  
	
	// Make sure everything is as expected or tip & exit.
	if(shape == nil || shape->body == nil || shape->data == nil) {  
	NSLog(@"Unexpected shape please debug here..."); return;}  
	
	// Lastly checks if the object is an XIShape of any kind and update its position accordingly.
	if([(NSObject *)shape->data isKindOfClass:[XIShape class]]) {  
		[(XIShape *)shape->data shapeWasUpdated];  
	}  
	else {NSLog(@"The shape data wasn't updateable using this code.");}
}  

- (void)stepSpace
{
	// Step one. This will move the object a bit, calculate collisions and new positions of physical objects.
	cpSpaceStep(space, -[[[XIGame sharedGame] lastUpdate] timeIntervalSinceNow]);
	
	// Go though all the objects in the scene's space and update their's position etc.
	cpSpaceHashEach(space->activeShapes, &updateShape, nil);
}
#endif

#pragma mark -
#pragma mark Loop

#if TARGET_OS_IPHONE
- (void)update
{	
	// Exit from function if the game is paused and the layer doesn't ignore pause.
	if(!ignoresPause && [XIGame sharedGame].paused){return;}
	
	// Go though all the objects and send an update message to them.
	// Each object it responsible to update itself (position, angle, whatever) by implementing the update method.
	for(XIObject *object in objects)
	{
		[object update];
	}
	
	// If the scene has a space (has moving objets that use the physics engine), let the engine update the space.
	if(physicsEnabled && space)
	{
		[self stepSpace];
		/*NSTimeInterval targetSPF = [XIGame sharedGame].targetSPF;
		 NSTimeInterval deltaTime = -[[[XIGame sharedGame] lastUpdate] timeIntervalSinceNow];
		 timeAccumulator += deltaTime;
		 while(timeAccumulator >= targetSPF)
		 {
		 // Step one. This will move the object a bit, calculate collisions and new positions of physical objects.
		 cpSpaceStep(space, targetSPF);
		 timeAccumulator -= targetSPF;
		 }
		 // Go though all the objects in the scene's space and update their's position etc.
		 cpSpaceHashEach(space->activeShapes, &updateShape, nil);*/
	}
}
#endif

- (void)draw
{
	// Go through all the objects and send a draw message to them.
	// Each object is responsive to draw itself by implementing the draw method.
	for(XIObject *object in objects)
	{
#if TARGET_OS_IPHONE
		[object draw];
#else
		[object drawForSceneBuilder];
#endif
	}
}

#pragma mark -
#pragma mark Private methods

#if TARGET_OS_IPHONE
- (void)setupChipmunk
{
	// Create a space object.
	space = cpSpaceNew();  
	
	// Define a gravity vector. 
	space->gravity = cpv(0, kXIPhysicsGravityDefault);
	
#ifdef kXIDisableGravity
	space->gravity = cpv(0, 0);
#endif
	
	// Add some elastic effects to the simulation
	space->iterations = 10;
	space->elasticIterations = 10;
}
#endif

@end
